package com.atguigu.gmall.search.service;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.bean.ResponseVo;
import com.atguigu.gmall.pms.api.GmallPmsApi;
import com.atguigu.gmall.pms.entity.*;
import com.atguigu.gmall.search.pojo.*;
import com.atguigu.gmall.search.repository.GoodsRepository;
import com.atguigu.gmall.wms.api.GmallWmsApi;
import com.atguigu.gmall.wms.entity.WareSkuEntity;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.ParsedAggregation;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Service
public class SearchService {

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    @Autowired
    private GmallPmsApi gmallPmsApi;

    @Autowired
    private GmallWmsApi gmallWmsApi;

    @Autowired
    private GoodsRepository goodsRepository;


    public SearchResponseVo search(SearchParamVo searchParamVo) throws IOException {

        try {
            SearchRequest searchRequest = new SearchRequest(new String[]{"goods"},buildDsl(searchParamVo));
            SearchResponse searchResponse = this.restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            //System.out.println(searchResponse);

            SearchResponseVo responseVo = this.parseResult(searchResponse);
            responseVo.setPageNum(searchParamVo.getPageNum());
            responseVo.setPageSize(searchParamVo.getPageSize());
            return responseVo;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 解析搜索结果集
     * @param response
     * @return
     */
    private SearchResponseVo parseResult(SearchResponse response) {

        SearchResponseVo responseVo = new SearchResponseVo();

        SearchHits hits = response.getHits();
        //设置总记录数
        responseVo.setTotal(hits.getTotalHits());

        //hits中的hits数据分析获取sku等信息
        SearchHit[] searchHits = hits.getHits();

        //方便直接获取goods集合使用Stream的方式
        responseVo.setGoodsList(Stream.of(searchHits).map(searchHit->{
            //获取_source，反序列化为goods数据模型
            String source = searchHit.getSourceAsString();
            Goods goods = JSON.parseObject(source, Goods.class);

            //获取高亮结果集
            Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
            HighlightField highlightField = highlightFields.get("title");
            goods.setTitle(highlightField.fragments()[0].string()); //.fragments()[0]

            return goods;
        }).collect(Collectors.toList()));

        //解析aggregation结果集
        Aggregations aggregations = response.getAggregations();
        ParsedLongTerms brandIdAgg = aggregations.get("brandIdAgg");
        List<? extends Terms.Bucket> brandIdAggBuckets = brandIdAgg.getBuckets();
        if (!CollectionUtils.isEmpty(brandIdAggBuckets)){
            //把桶的集合转化为品牌集合
            responseVo.setBrands(brandIdAggBuckets.stream().map(bucket ->{
                BrandEntity brandEntity = new BrandEntity();
                //每个桶中的key就是品牌的id
                brandEntity.setId(((Terms.Bucket) bucket).getKeyAsNumber().longValue());

                //获取所有的子聚合
                Aggregations subAggs = ((Terms.Bucket) bucket).getAggregations();

                //获取品牌名称的子聚合
                ParsedStringTerms brandNameAgg = subAggs.get("brandNameAgg");
                //获取品牌名称子聚合中的桶
                List<? extends Terms.Bucket> brandNameAggBuckets = brandNameAgg.getBuckets();
                if (!CollectionUtils.isEmpty(brandNameAggBuckets)){
                    //品牌名称这个子聚合应该有且仅有一个元素
                    brandEntity.setName(brandNameAggBuckets.get(0).getKeyAsString());
                }

                //获取logo子聚合
                ParsedStringTerms logoAgg = subAggs.get("logoAgg");
                List<? extends Terms.Bucket> logoAggBuckets = logoAgg.getBuckets();
                if (!CollectionUtils.isEmpty(logoAggBuckets)){
                    brandEntity.setLogo(logoAggBuckets.get(0).getKeyAsString());
                }

                return brandEntity;
            }).collect(Collectors.toList()));
        }

        //分类
        ParsedLongTerms categoryIdAgg = aggregations.get("categoryIdAgg");
        List<? extends Terms.Bucket> categoryIdAggBuckets = categoryIdAgg.getBuckets();
        if (!CollectionUtils.isEmpty(categoryIdAggBuckets)){
            responseVo.setCategories(categoryIdAggBuckets.stream().map(bucket ->{
                CategoryEntity categoryEntity = new CategoryEntity();
                categoryEntity.setId(((Terms.Bucket) bucket).getKeyAsNumber().longValue());

                ParsedStringTerms categoryNameAgg = bucket.getAggregations().get("categoryNameAgg");;
                List<? extends Terms.Bucket> nameAggBuckets = categoryNameAgg.getBuckets();
                if (!CollectionUtils.isEmpty(nameAggBuckets)){

                    categoryEntity.setName(nameAggBuckets.get(0).getKeyAsString());

                }

                return categoryEntity;
            }).collect(Collectors.toList()));
        }

        //规格参数的聚合结果集
        ParsedNested attrAgg = aggregations.get("attrAgg");
        // 获取嵌套聚合结果集中规格参数id子聚合
        ParsedLongTerms subAttrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
        List<? extends Terms.Bucket> attrBuckets = subAttrIdAgg.getBuckets();
        if (!CollectionUtils.isEmpty(attrBuckets)){
            responseVo.setFilters(attrBuckets.stream().map(bucket -> {
                SearchResponseAttrVo searchResponseAttrVo = new SearchResponseAttrVo();

                //当前桶中的key就是规格参数id
                searchResponseAttrVo.setAttrId(((Terms.Bucket) bucket).getKeyAsNumber().longValue());
                //获取当前桶中的所有子聚合
                Aggregations subAggs = ((Terms.Bucket) bucket).getAggregations();


                //获取规格参数名称的子聚合
                ParsedStringTerms attrNameAgg = subAggs.get("attrNameAgg");
                List<? extends Terms.Bucket> nameBuckets = attrNameAgg.getBuckets();
                    //获取规格参数名称子聚合中的桶，有且仅有一个元素
                if (!CollectionUtils.isEmpty(nameBuckets)){
                    searchResponseAttrVo.setAttrName(nameBuckets.get(0).getKeyAsString());
                }

                //获取规格参数值的子聚合
                ParsedStringTerms attrValueAgg = subAggs.get("attrValueAgg");
                List<? extends Terms.Bucket> valueAggBuckets = attrValueAgg.getBuckets();
                if (!CollectionUtils.isEmpty(valueAggBuckets)){
                    searchResponseAttrVo.setAttrValues(valueAggBuckets.stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList()));
                }

                return searchResponseAttrVo;

            }).collect(Collectors.toList()));
        }


        return responseVo;
    }

    /**
     * 构建查询DSL语句
     * @param searchParamVo
     * @return
     */
    private SearchSourceBuilder buildDsl(SearchParamVo searchParamVo) {

        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        String keyword = searchParamVo.getKeyword();
        if (StringUtils.isBlank(keyword)){
            throw new RuntimeException("搜索关键字不能为空！");
        }

        //1. 构建查询条件（bool查询）
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        sourceBuilder.query(boolQueryBuilder);

        //1.1 匹配查询
        boolQueryBuilder.must(QueryBuilders.matchQuery("title",keyword).operator(Operator.AND));
        //1.2 过滤
        //1.2.1 品牌过滤
        List<Long> brandId = searchParamVo.getBrandId();
        if (!CollectionUtils.isEmpty(brandId)){
            boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId",brandId));
        }

        //1.2.2 分类过滤
        List<Long> categoryId = searchParamVo.getCategoryId();
        if (!CollectionUtils.isEmpty(categoryId)){
            boolQueryBuilder.filter(QueryBuilders.termsQuery("categoryId",categoryId));
        }

        // 1.2.3 价格区间过滤
        Double priceFrom = searchParamVo.getPriceFrom();
        Double priceTo = searchParamVo.getPriceTo();
        if (priceFrom != null || priceTo != null){
            RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("price");
            if (priceFrom != null){
                rangeQuery.gte(priceFrom);
            }
            if (priceTo != null){
                rangeQuery.lte(priceTo);
            }
            boolQueryBuilder.filter(rangeQuery);
        }

        // 1.2.4 是否有货
        Boolean store = searchParamVo.getStore();
        if (store != null){
            boolQueryBuilder.filter(QueryBuilders.termQuery("store",store));
        }

        // 1.2.5 构建规格参数过滤
        List<String> props = searchParamVo.getProps();
        if (!CollectionUtils.isEmpty(props)){
            props.forEach(prop -> {  // 4:8G-12G
                String[] attr = StringUtils.split(prop, ":");//用:分割
                if (attr != null && attr.length == 2){ //当分割后的值不为空，并长度为2，才是合法的
                    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
                    boolQueryBuilder.filter(QueryBuilders.nestedQuery("searchAttrs",boolQuery, ScoreMode.None));
                    //数组的第一个为规格参数id
                    boolQuery.must(QueryBuilders.termQuery("searchAttrs.attrId",attr[0]));

                    //规格参数值：8G-12G 再分割
                    String[] attrValues = StringUtils.split(attr[1], "-");
                    boolQuery.must(QueryBuilders.termsQuery("searchAttrs.attrValue",attrValues));
                }
            });
        }

        // 2.构建排序条件 0-得分排序 1-价格降序 2-价格升序 3-销量降序 4-新品降序
        Integer sort = searchParamVo.getSort();
        if (sort != null){
            switch (sort){
                case 1: sourceBuilder.sort("price", SortOrder.DESC); break;
                case 2: sourceBuilder.sort("price", SortOrder.ASC); break;
                case 3: sourceBuilder.sort("sales", SortOrder.DESC); break;
                case 4: sourceBuilder.sort("createTime", SortOrder.DESC); break;
                default:
                    sourceBuilder.sort("_score",SortOrder.DESC);
                    break;
            }
        }

        // 3.构建分页条件
        Integer pageNum = searchParamVo.getPageNum();
        Integer pageSize = searchParamVo.getPageSize();
        sourceBuilder.from((pageNum - 1) * pageSize);
        sourceBuilder.size(pageSize);

        // 4.构建高亮
        sourceBuilder.highlighter(new HighlightBuilder().field("title")
                .preTags("<font style='color:red;'>").postTags("</font>"));

        // 5.构建聚合条件
        // 5.1 构建品牌聚合
        sourceBuilder.aggregation(AggregationBuilders.terms("brandIdAgg").field("brandId")
                .subAggregation(AggregationBuilders.terms("brandNameAgg").field("brandName"))
                .subAggregation(AggregationBuilders.terms("logoAgg").field("logo")));

        // 5.2 构建分类聚合
        sourceBuilder.aggregation(AggregationBuilders.terms("categoryIdAgg").field("categoryId")
                .subAggregation(AggregationBuilders.terms("categoryNameAgg").field("categoryName")));

        // 5.3 构建规格参数聚合--嵌套聚合
        sourceBuilder.aggregation(AggregationBuilders.nested("attrAgg","searchAttrs")
                .subAggregation(AggregationBuilders.terms("attrIdAgg").field("searchAttrs.attrId")
                        .subAggregation(AggregationBuilders.terms("attrNameAgg").field("searchAttrs.attrName"))
                        .subAggregation(AggregationBuilders.terms("attrValueAgg").field("searchAttrs.attrValue"))));

        // 6.构建结果集过滤
        sourceBuilder.fetchSource(new String[]{"skuId","title","subtitle","price","defaultImage"},null);

        //System.out.println(sourceBuilder);
        return sourceBuilder;
    }

    public void createIndex(Long id) {

        ResponseVo<SpuEntity> spuEntityResponseVo = this.gmallPmsApi.querySpuById(id);
        SpuEntity spuEntity = spuEntityResponseVo.getData();

        ResponseVo<List<SkuEntity>> responseVo = this.gmallPmsApi.querySkusBySpuId(spuEntity.getId());
        List<SkuEntity> skuEntities = responseVo.getData();
        //sku集合不为空才保存到goods中
        if (!CollectionUtils.isEmpty(skuEntities)) {

            //只有sku不为空时，才需要取查询品牌，如果sku为空了，不需要转化为goods，进而就不需要品牌
            ResponseVo<BrandEntity> brandEntityResponseVo = this.gmallPmsApi.queryBrandById(spuEntity.getBrandId());
            BrandEntity brandEntity = brandEntityResponseVo.getData();

            ResponseVo<CategoryEntity> categoryEntityResponseVo = this.gmallPmsApi.queryCategoryById(spuEntity.getCategoryId());
            CategoryEntity categoryEntity = categoryEntityResponseVo.getData();

            //3.将sku以及所需的信息保存到goods集合中
            List<Goods> goodsList = skuEntities.stream().map(skuEntity -> {
                Goods goods = new Goods();

                goods.setSkuId(skuEntity.getId());
                goods.setDefaultImage(skuEntity.getDefaultImage());
                goods.setTitle(skuEntity.getTitle());
                goods.setSubtitle(skuEntity.getSubtitle());
                goods.setPrice(skuEntity.getPrice());
                //新品排序 spu属性
                goods.setCreateTime(spuEntity.getCreateTime());
                //获取销量和判断是否有货，为set方法备用
                ResponseVo<List<WareSkuEntity>> listResponseVo = this.gmallWmsApi.queryWaresBySkuId(skuEntity.getId());
                List<WareSkuEntity> wareSkuEntities = listResponseVo.getData();
                if (!CollectionUtils.isEmpty(wareSkuEntities)) {
                    goods.setSales(wareSkuEntities.stream().mapToLong(WareSkuEntity::getSales).reduce((a, b) -> a + b).getAsLong());
                    goods.setStore(wareSkuEntities.stream().anyMatch(wareSkuEntity -> wareSkuEntity.getStock() - wareSkuEntity.getStockLocked() > 0));
                }

                //聚合字段，品牌、分类
                if (brandEntity != null) {
                    goods.setBrandName(brandEntity.getName());
                    goods.setBrandId(brandEntity.getId());
                    goods.setLogo(brandEntity.getLogo());
                }
                if (categoryEntity != null) {
                    goods.setCategoryId(categoryEntity.getId());
                    goods.setCategoryName(categoryEntity.getName());
                }

                //goods中属性searchAttrs中包含 spu基本属性的设置和sku销售属性的设置
                List<SearchAttrValue> searchAttrValues = new ArrayList<>();

                ResponseVo<List<SpuAttrValueEntity>> responseVo2 = this.gmallPmsApi.querySearchAttrValuesByCidAndSpuId(skuEntity.getCategoryId(), skuEntity.getSpuId());
                List<SpuAttrValueEntity> spuAttrValueEntities = responseVo2.getData();
                //用stream，不用foreach
                if (!CollectionUtils.isEmpty(spuAttrValueEntities)) {
                    searchAttrValues.addAll(spuAttrValueEntities.stream().map(spuAttrValueEntity -> {
                        SearchAttrValue searchAttrValue = new SearchAttrValue();
                        BeanUtils.copyProperties(spuAttrValueEntity, searchAttrValue);
                        return searchAttrValue;
                    }).collect(Collectors.toList()));
                }

                ResponseVo<List<SkuAttrValueEntity>> responseVo1 = this.gmallPmsApi.querySearchAttrValuesByCidAndSkuId(skuEntity.getCategoryId(), skuEntity.getId());
                List<SkuAttrValueEntity> skuAttrValueEntities = responseVo1.getData();

                if (!CollectionUtils.isEmpty(skuAttrValueEntities)) {
                    searchAttrValues.addAll(skuAttrValueEntities.stream().map(skuAttrValueEntity -> {
                        SearchAttrValue searchAttrValue = new SearchAttrValue();
                        BeanUtils.copyProperties(skuAttrValueEntity, searchAttrValue);
                        return searchAttrValue;
                    }).collect(Collectors.toList()));
                }

                goods.setSearchAttrs(searchAttrValues);

                return goods;
            }).collect(Collectors.toList());

            this.goodsRepository.saveAll(goodsList);
        }
    }
}
